package com.zy.je.common.mybatis.ext.util;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.zy.je.common.core.constant.QueryType;
import com.zy.je.common.core.constant.StringPool;
import com.zy.je.common.core.utils.CheckUtil;
import com.zy.je.common.core.utils.collect.ListUtil;
import com.zy.je.common.core.utils.lang.ObjectUtil;
import com.zy.je.common.core.utils.lang.StringUtil;
import com.zy.je.common.mybatis.ext.model.base.BaseEntity;
import org.dromara.hutool.core.bean.BeanUtil;
import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.text.CharSequenceUtil;
import org.dromara.hutool.core.text.StrUtil;

import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author zy
 */
public class WrapperUtil {

    public static final String ORDER_DESC_STR = " desc";
    public static final String ORDER_ASC_STR = " asc";
    public static final String EMPTY_STR = "";

    public WrapperUtil() {
    }

    public static <T extends BaseEntity> QueryWrapper<T> setOrderBy(QueryWrapper<T> wrapper, String orderByStr) {
        if (StrUtil.isBlank(orderByStr)) {
            return wrapper;
        }
        //优先级：QueryEnchance._q.orderBy  >  QueryEnchance._orderBy
        String[] orderByColumns = orderByStr.split(",");
        for (String column : orderByColumns) {
            String trimStr = CharSequenceUtil.trimSuffix(column);
            if (StrUtil.endWithIgnoreCase(StrUtil.trimSuffix(column), ORDER_DESC_STR)) {
                wrapper.orderByDesc(StringUtil.uncamelCase(StrUtil.replaceIgnoreCase(trimStr, ORDER_DESC_STR, EMPTY_STR)));
            } else {
                wrapper.orderByAsc(StringUtil.uncamelCase(StrUtil.replaceIgnoreCase(trimStr, ORDER_ASC_STR, EMPTY_STR)));
            }
        }
        return wrapper;
    }

    public static <T extends BaseEntity> QueryWrapper<T> toWrapper(T m) {
        return toWrapper(m, null);
    }

    public static <T extends BaseEntity> QueryWrapper<T> toWrapper(T m, Class<T> entityClass) {
        //暂存增强的查询条件
        Map<String, String> queryEnhanceMap = MapUtil.emptyIfNull(m.buildQ());
        m.set_q(null);

        //将实体对象转成map
        Map<String, Object> entityMap = MapUtil.emptyIfNull(BeanUtil.beanToMap(m)).entrySet().stream()
                .filter(it -> null != it.getValue())
                .collect(Collectors.toMap(it -> it.getKey(), it -> it.getValue(), (v1, v2) -> v1));

        Map<String, TableFieldInfo> tableFieldInfoMap = getTableFiledInfoMap(entityClass);

        //合并实体map和增强的查询条件
        Map<String, Object> mergedParamMap = Stream.concat(
                        entityMap.entrySet().stream()
                                .filter(it -> !CheckUtil.isBlank(tableFieldInfoMap.get(it.getKey())))
                        , queryEnhanceMap.entrySet().stream()
                )
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v2));

        return toWrapper(mergedParamMap, tableFieldInfoMap);
    }

    private static <T extends BaseEntity> Map<String, TableFieldInfo> getTableFiledInfoMap(Class<T> entityClass) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        if (null != tableInfo) {
            return tableInfo.getFieldList().stream()
                    .collect(Collectors.toMap(
                            it -> it.getField().getName(), it -> it, (v1, v2) -> v2
                    ));

        }
        return null;
    }

    /**
     * QueryMap转成Wrapper
     *
     * @param paramMap
     * @return
     */
    private static <T extends BaseEntity> QueryWrapper<T> toWrapper(Map<String, Object> paramMap, Map<String, TableFieldInfo> tableFieldInfoMap) {
        if (CheckUtil.isBlank(paramMap)) {
            return null;
        }

        QueryWrapper<T> condition = new QueryWrapper<T>();
        paramMap.entrySet().forEach(entry -> {
            String key = entry.getKey();

            if (isDangerousOperation(key)) {
                return;
            }

            if (key.startsWith(StringPool.UNDERSCORE)) {
                key = StringPool.STAR + key;
            }
            String[] keys = StringUtil.split(key, StringPool.UNDERSCORE);
            int len = keys.length;
            String property = null;
            if (len <= 1) {
                property = transToColumn(tableFieldInfoMap, entry.getKey());
                condition.eq(property, entry.getValue());
            } else if (len > 1) {
                String opt = keys[len - 1];
                property = transToColumn(tableFieldInfoMap, key.replace(StringPool.UNDERSCORE.concat(opt), ""));
                Object value = entry.getValue();

                QueryType queryType = QueryType.getInstance(opt);
                if (null == queryType) {
                    condition.eq(property, value);
                } else {
                    switch (queryType) {
                        case EQ:
                            condition.eq(property, value);
                            break;
                        case GT:
                            condition.gt(property, value);
                            break;
                        case GTE:
                            condition.ge(property, value);
                            break;
                        case LT:
                            condition.lt(property, value);
                            break;
                        case LTE:
                            condition.le(property, value);
                            break;
                        case NE:
                            condition.ne(property, value);
                            break;
                        case CTRL_LIKE:
                            //
                            String valueStr = (String) value;
                            if (null != value) {
                                if (valueStr.startsWith(StringPool.PERCENT) && valueStr.endsWith(StringPool.PERCENT)) {
                                    condition.like(property, value);
                                } else if (valueStr.startsWith(StringPool.PERCENT)) {
                                    condition.likeLeft(property, value);
                                } else if (valueStr.endsWith(StringPool.PERCENT)) {
                                    condition.likeRight(property, value);
                                } else {
                                    condition.eq(property, value);
                                }
                            }
                            break;
                        case LEFT_LIKE:
                            condition.likeLeft(property, value);
                            break;
                        case RIGHT_LIKE:
                            condition.likeRight(property, value);
                            break;
                        case LIKE:
                            condition.like(property, value);
                            break;
                        case IN:
                            if (value instanceof String) {
                                condition.in(property, Arrays.asList(((String) value).split(StringPool.COMMA, -1)));
                            } else {
                                condition.in(property, (Collection<?>) value);
                            }
                            break;
                        case NOT_IN:
                            if (value instanceof String) {
                                condition.notIn(property,
                                        Arrays.asList(((String) value).split(StringPool.COMMA, -1)));
                            } else {
                                condition.notIn(property, (Collection<?>) value);
                            }
                            break;
                        case IS_NOT_NULL:
                            condition.isNotNull(property);
                            break;
                        case IS_NULL:
                            condition.isNull(property);
                            break;
                        case ORDER_BY:
                            setOrderBy(condition, value.toString());
                            break;
                        case SELECT_FIELD:
                            List<String> selectFieldList = ListUtil.newArrayList(ObjectUtil.toString((String) value, "").split(","))
                                    .stream().filter(it -> !StringUtil.isBlank(it))
                                    .collect(Collectors.toList());
                            condition.select(selectFieldList.toArray(new String[selectFieldList.size()]));
                            break;
                        case NONE:
                            // 表示无操作，不参与查询
                            break;
                        default:
                            condition.eq(property, value);
                    }
                }
            }
        });
        return condition;
    }

    private static String transToColumn(Map<String, TableFieldInfo> tableFieldInfoMap, String fieldName) {
        if (CheckUtil.isBlank(tableFieldInfoMap)) {
            return StringUtil.uncamelCase(fieldName);
        }
        TableFieldInfo tableFieldInfo = tableFieldInfoMap.get(fieldName);
        if (CheckUtil.isBlank(tableFieldInfo)) {
            return StringUtil.uncamelCase(fieldName);
        }
        return tableFieldInfo.getColumn();
    }


    public static String fmtOrderBy(String orderByStr) {
        if (CheckUtil.isBlank(orderByStr)) {
            return null;
        }
        List<String> tmpList = ListUtil.newArrayList();
        String[] orderByColumns = orderByStr.split(StringPool.COMMA);
        for (String column : orderByColumns) {
            String trimStr = CharSequenceUtil.trimSuffix(column);
            tmpList.add(StringUtil.uncamelCase(trimStr));
        }
        return tmpList.stream().collect(Collectors.joining(StringPool.COMMA));
    }

    private static boolean isDangerousOperation(String opt) {
        // SQL过滤，防止注入
        String reg = "(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|(\\b(select|update|and|or|delete|insert|trancate|char|into|substr|ascii|declare|exec|count|master|drop|execute|alter|sleep)\\b)";
        Pattern sqlPattern = Pattern.compile(reg, Pattern.CASE_INSENSITIVE);
        //sql注入过滤
        if (sqlPattern.matcher(opt).find()) {
            return true;
        }
        return false;
    }

    /**
     * 格式化orderBy字段为OrderItem列表
     *
     * @param orderBy
     * @return
     */
    public static List<OrderItem> fmtOrderBy2OrderItems(String orderBy) {
        List<OrderItem> ords = new ArrayList<>();
        if (StrUtil.isBlank(orderBy)) {
            return ords;
        }

        List<String> orderCauseList = Arrays.asList(orderBy.trim().split(StringPool.COMMA));
        orderCauseList.forEach(item -> {
            //sql注入过滤
            if (isDangerousOperation(item)) {
                return;
            }

            // 第一项为排序字段
            OrderItem orderItem = new OrderItem();
            if (StrUtil.endWithIgnoreCase(CharSequenceUtil.trimSuffix(item), ORDER_DESC_STR)) {
                orderItem.setColumn(StringUtil.uncamelCase(StrUtil.replaceIgnoreCase(item, ORDER_DESC_STR, EMPTY_STR)));
                orderItem.setAsc(false);
            } else {
                orderItem.setColumn(StringUtil.uncamelCase(StrUtil.replaceIgnoreCase(item, ORDER_ASC_STR, EMPTY_STR)));
                orderItem.setAsc(true);
            }
            ords.add(orderItem);
        });

        return ords;
    }
}
